package com.tc.vms.metric;

import com.tc.vms.log.VmsClientLogger;
import com.yammer.metrics.Metrics;
import com.yammer.metrics.core.*;
import com.yammer.metrics.core.Timer;
import org.slf4j.Logger;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * Created by yonghua.zhang on 2016/1/28.
 */
public abstract class VmsMetricsGroup {
    protected final static Logger LOGGER = VmsClientLogger.getLog();
    protected final static List<MetricName> producerMetricNameList;
    protected final static List<MetricName> consumerMetricNameList;

    static {
        ArrayList<MetricName> tmpProdMetricNameList = new ArrayList<MetricName>(4);
        tmpProdMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.producer", "VmsProducerMetrics", "SuccessMessagePerSec"));
        tmpProdMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.producer", "VmsProducerMetrics", "FailureMessagePerSec"));
        tmpProdMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.producer", "VmsProducerMetrics", "BytesPerSec"));
        tmpProdMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.producer", "VmsProducerMetrics", "ProducerSendRateAndTimeMs"));
        producerMetricNameList = Collections.unmodifiableList(tmpProdMetricNameList);

        ArrayList<MetricName> tmpConsumeMetricNameList = new ArrayList<MetricName>(6);

        tmpConsumeMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.consumer", "VmsConsumerMetrics", "ConsumeSuccessPerSec"));
        tmpConsumeMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.consumer", "VmsConsumerMetrics", "ConsumeFailurePerSec"));
        tmpConsumeMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.consumer", "VmsConsumerMetrics", "ReconsumeSuccessPerSec"));
        tmpConsumeMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.consumer", "VmsConsumerMetrics", "ReconsumeFailurePerSec"));
        tmpConsumeMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.consumer", "VmsConsumerMetrics", "TotalCostTimeMs"));
        tmpConsumeMetricNameList.add(new MetricName("com.vip.platform.middleware.vms.consumer", "VmsConsumerMetrics", "UserCostTimeMs"));
        consumerMetricNameList = Collections.unmodifiableList(tmpConsumeMetricNameList);
    }

    private MetricName metricName(String name, Map<String, String> tags) {
        Class<?> klass = this.getClass();
        String pkg = klass.getPackage() == null ? "" : klass.getPackage().getName();
        String simpleName = klass.getSimpleName().replaceAll("\\$$", "");
        return explicitMetricName(pkg, simpleName, name, tags);
    }

    private String toScope(Map<String, String> tags) {
        if (tags == null || tags.size() == 0) return "";
        ArrayList<Map.Entry<String, String>> tagList = new ArrayList<Map.Entry<String, String>>();
        for (Map.Entry<String, String> tag : tags.entrySet()) {
            String value = tag.getValue();
            if (value != null && !value.isEmpty()) {
                tagList.add(tag);
            }
        }

        if (tagList.isEmpty()) return "";

        Collections.sort(tagList, new Comparator<Map.Entry<String, String>>() {
            @Override
            public int compare(Map.Entry<String, String> e1, Map.Entry<String, String> e2) {
                return e1.getKey().hashCode() - e2.getKey().hashCode();
            }
        });

        StringBuilder sb = new StringBuilder();
        for (Map.Entry<String, String> e : tagList) {
            sb.append(String.format("%s.%s", e.getKey(), e.getValue().replaceAll("\\.", "_")));
            sb.append(".");
        }

        if (sb.length() > 0) {
            sb.setLength(sb.length() - 1);
        }
        return sb.toString();
    }

    private String toMBeanName(Map<String, String> tags) {
        if (tags == null || tags.size() == 0) return "";
        ArrayList<Map.Entry<String, String>> tagList = new ArrayList<Map.Entry<String, String>>();
        for (Map.Entry<String, String> tag : tags.entrySet()) {
            String value = tag.getValue();
            if (value != null && !value.isEmpty()) {
                tagList.add(tag);
            }
        }

        if (tagList.isEmpty()) return "";

        StringBuilder sb = new StringBuilder();
        if (tagList.size() > 0) {
            for (Map.Entry<String, String> e : tagList) {
                sb.append(String.format("%s=%s", e.getKey(), e.getValue()));
                sb.append(",");
            }

            if (sb.length() > 0) {
                sb.setLength(sb.length() - 1);
            }
        }
        return sb.toString();
    }

    private MetricName explicitMetricName(String group, String typeName, String name, Map<String, String> tags) {
        StringBuilder nameBuilder = new StringBuilder();
        nameBuilder.append(group)
                .append(":type=")
                .append(typeName);

        if (name.length() > 0) {
            nameBuilder.append(",name=").append(name);
        }

        String scope = toScope(tags);
        String tagsName = toMBeanName(tags);

        if (!tagsName.isEmpty()) {
            nameBuilder.append(",").append(tagsName);
        }
        return new MetricName(group, typeName, name, scope, nameBuilder.toString());
    }

    protected <T> Gauge<T> newGauge(String name, Gauge<T> metric, Map<String, String> tags) {
        return Metrics.defaultRegistry().newGauge(metricName(name, tags), metric);
    }


    protected Meter newMeter(String name, String eventType, TimeUnit timeUnit, Map<String, String> tags) {
        return Metrics.defaultRegistry().newMeter(metricName(name, tags), eventType, timeUnit);
    }


    protected Histogram newHistogram(String name, Boolean biased, Map<String, String> tags) {
        return Metrics.defaultRegistry().newHistogram(metricName(name, tags), biased);
    }

    protected Timer newTimer(String name, TimeUnit durationUnit, TimeUnit rateUnit, Map<String, String> tags) {
        return Metrics.defaultRegistry().newTimer(metricName(name, tags), durationUnit, rateUnit);
    }

    protected void removeMetric(String name, Map<String, String> tags) {
        Metrics.defaultRegistry().removeMetric(metricName(name, tags));
    }

    public void removeAllMetricsInList(List<MetricName> metricNameList, String clientId) {
        Pattern pattern = Pattern.compile(".*client\\.id=" + clientId + ".*");
        for (MetricName metric : metricNameList) {
            Set<MetricName> registeredMetrics = Metrics.defaultRegistry().allMetrics().keySet();
            for (MetricName registeredMetric : registeredMetrics) {
                if (registeredMetric.getGroup().equals(metric.getGroup()) &&
                        registeredMetric.getName().equals(metric.getName()) &&
                        registeredMetric.getType().equals(metric.getType())) {
                    Matcher matcher = pattern.matcher(registeredMetric.getMBeanName());
                    if (matcher.find()) {
                        int beforeRemovalSize = Metrics.defaultRegistry().allMetrics().keySet().size();
                        Metrics.defaultRegistry().removeMetric(registeredMetric);
                        int afterRemovalSize = Metrics.defaultRegistry().allMetrics().keySet().size();
                        LOGGER.trace(String.format("Removing metric %s. Metrics registry size reduced from %d to %d",
                                registeredMetric, beforeRemovalSize, afterRemovalSize));
                    }
                }
            }
        }
    }

    public void removeAllConsumerMetrics(String clientId) {
        removeAllMetricsInList(consumerMetricNameList, clientId);
    }

    public void removeAllProducerMetrics(String clientId) {
        removeAllMetricsInList(producerMetricNameList, clientId);
    }

}
